import sys
import os
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QLabel,QCompleter
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QThread,QMetaObject
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot, QObject
from PyQt5.QtWidgets import QMessageBox
import warnings
import threading
import logging
import json
import importlib.util
from common.rpc_z import Rpc  # 导入tester-rpc通讯类
from ui.localFileToTester_UI import Ui_rpc_test  # 从生成的UI文件导入
from common.common import common # 导入奇奇怪怪的常用方法类
from common.update_fw import TesterControls # 导入对mix文件的操作类
common = common()

# sys.path.append(os.path.join(os.path.dirname(__file__)))

warnings.filterwarnings("ignore", category=DeprecationWarning)

global app_path

if getattr(sys, 'frozen', False):  # 检查是否已打包为可执行文件
    app_path = os.path.dirname(sys.executable)
    app_path = app_path + "/"
else:
    app_path = "./"
print(f"app运行路径: {app_path}")


def enable_drag_drop(line_edit: QLineEdit):
    """
    使给定的 QLineEdit 具有拖放功能。
    """
    def dragEnterEvent(event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dragMoveEvent(event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if urls:
                # 获取第一个文件/文件夹的路径
                path = urls[0].toLocalFile()
                line_edit.setText(path)
            event.acceptProposedAction()
        else:
            event.ignore()

    # 设置允许拖放事件
    line_edit.setAcceptDrops(True)

    # 重写拖放事件处理方法
    line_edit.dragEnterEvent = dragEnterEvent
    line_edit.dragMoveEvent = dragMoveEvent
    line_edit.dropEvent = dropEvent

class WorkerSSH(object):
    """对MIX的常用动作，用异步的方式完成"""
    def __init__(self,ui_data):
        super().__init__()
        self.ui_data = ui_data  # UI相关信息或UI类实例
        self.ip_list = self.get_channel_ip(self.ui_data) 
        self.username = ui_data.lineEdit_name.text() or "root"
        self.password = ui_data.lineEdit_password.text() or "123456"

    def get_channel_ip(self,data):
        ip_list = []
        for w in data.channel_widgets:
            dictBuff = w.get_config()
            if dictBuff["ip"] not in  ip_list:
                ip_list.append(dictBuff["ip"])
        return ip_list

    def task(self,method,ip_str):
        print(f"线程: {ip_str}开始启动")
        tester = TesterControls(ip_str,username=self.username,password=self.password)
        if method == "get":
            tester.get_log_to_local(local_file_path=self.ui_data.lineEdit_local.text(), remote_file_path=self.ui_data.lineEdit_remote.text())
        elif method == "push":
            tester.push_file_to_ip(local_file_path=self.ui_data.lineEdit_local.text(), remote_file_path=self.ui_data.lineEdit_remote.text())
        elif method == "reboot":
            tester.run_command_with_sudo("reboot")
        elif method == "sudo":
            tester.run_command_with_sudo("chmod 777 -R /mix")
        return "pass"

    def start_multiple_threads_with_timer(self,method):
        """
        立即启动多个线程.
        """
        threads = []
        timers = []
        for ip_str in self.ip_list:
            print(f"启动线程: {ip_str}")
            worker_thread = threading.Thread(
                target=self.task, 
                args=(method, ip_str)
            )
            worker_thread.do_run = True
            worker_thread.start()
            threads.append((ip_str, worker_thread))

            # # 创建停止定时器
            # def stop_worker(name=ip_str, thread=worker_thread):
            #     print(f"[{datetime.now().strftime('%H:%M:%S')}] 停止 {name}...")
            #     thread.do_run = False
            #     thread.join(timeout=2.0)
            #     print(f"{name} 已停止")
            
            # stop_timer = threading.Timer(5, stop_worker)
            # stop_timer.start()
            # timers.append(stop_timer)
        return threads,timers

    def ssh_run(self,method):
        print(self.ip_list)
        print(self.username)
        print(self.password)
        threads, timers = self.start_multiple_threads_with_timer(method)






class WorkerThread(QThread):
    """对RPC的常用动作，用多线程的方式完成"""
    finished_signal = Signal(int)  # 信号
    label_add_tips = Signal(int)
    update_label = Signal(int,str,str)

    def __init__(self, ui_data, slotInfo,thread_ID):
        super().__init__()
        self.ui_data = ui_data  # UI相关信息或UI类实例
        self.stop_event = threading.Event()  # 用于线程停止的信号
        self.thread_ID = thread_ID#线程id，记录线程开始的先后顺序
        self.debug_rpc_flag = False
        self.slotInfo = slotInfo
        self.timeout = 30
        self.method = ""
        self.args_str = ""
        self.tester = None
        self.timeout_duration = 30000  # 超时时间，单位为毫秒
        self.send_rpc_flag = False


    def run(self):
        self.tester = Rpc(ip_tester = self.slotInfo["ip"],port_tester=self.slotInfo["port"])
        first_time = True
        while not self.stop_event.is_set() and self.tester.ret_tester() != None:
            if first_time:
                first_time = False
                if self.thread_ID == 1:
                    print(self.slotInfo["channel"])
                    all_methods_dict = self.tester.send_rpc("server.all_methods")
                    self.label_add_tips.emit(self.slotInfo["channel"])
                logging.info(f"通道 {self.slotInfo['channel']} 已经建立rpc通信")
            QThread.msleep(100)
            if self.send_rpc_flag == True:
                self.send_rpc_flag = False
                self.rpc_main()
        self.finished_signal.emit(self.slotInfo["channel"])  # 任务完成，发射信号

    def execute_rpc(self):
        method_str = self.method
        args_str = self.args_str
        args_str = ' '.join(args_str.split())
        args_list = args_str.split(" ") 
        kwargs = {}
        new_table = []

        for item in args_list:
            parts = item.split('=')
            if len(parts) == 2 and str(parts[0]).strip() and str(parts[1]).strip():
                key = str(parts[0]).strip()
                value = str(parts[1]).strip()
                kwargs[key] = value
            else:
                new_table.append(item)
        # kwargs["timeout_ms"] = 3000
        print(method_str)
        print(new_table)
        print(kwargs)
        data_dict = self.tester.send_rpc(method_str, *new_table, **kwargs)
        return data_dict

    def rpc_main(self):
        try:
            result = self.execute_rpc()
            logging.info(f"slots{self.slotInfo['channel']}-[rpc_result]: {result}")
        except ValueError:
            self.stop_event.set()

    @Slot()
    def send_rpc_method(self):
        self.method = self.ui_data.lineEdit_method.text()
        self.args_str = self.ui_data.lineEdit_arg.text()
        if not self.method or self.method == "":
            logging.info(f"slot{self.slotInfo['channel']}请输入rpc命令")
            return None
        self.send_rpc_flag = True
        logging.info(f"slot{self.slotInfo['channel']}-[rpc_method]:{self.method}")
        logging.info(f"slot{self.slotInfo['channel']}-[rpc_args]: {self.args_str}")

    @Slot()
    def stop(self):
        print("stop")
        self.stop_event.set()

# @Slot()解决了 “QMetaObject::invokeMethod: No such method WorkerThread::send_rpc()” 错误的原因
# 因为它告诉 Qt 的元对象系统 send_rpc 是一个有效的槽，可以被安全地调用。这是实现线程间通信和确保程序稳定性的关键一步。

# 自定义日志处理器
class QTextEditLogger(logging.Handler):
    def __init__(self, text_edit):
        super().__init__()
        self.text_edit = text_edit

    def emit(self, record):
        msg = self.format(record)
        self.text_edit.append(msg)

def init_logging(textEdit,log_level=logging.INFO,title_name=""):
    # 配置日志记录器
    logger = logging.getLogger()
    logger.setLevel(log_level)

    # 清除已有的同类型处理器
    for handler in logger.handlers[:]:
        if isinstance(handler, QTextEditLogger):
            logger.removeHandler(handler)

    # 创建自定义日志处理器
    log_handler = QTextEditLogger(textEdit)
    log_handler.setLevel(log_level)
    if title_name == "script":
        log_handler.setFormatter(logging.Formatter('%(message)s'))
    elif title_name == "SSH":
        log_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
    else:
        log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))

    # 添加处理器到日志记录器
    logger.addHandler(log_handler)

class ChannelConfig(QWidget):
    def __init__(self, channel_num, startNum=33,setp = 1,mixVer="mix7",parent=None):
        super().__init__(parent)
        self.channel_num = channel_num
        
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        
        self.label = QLabel(f"通道 {channel_num + 1}:")
        layout.addWidget(self.label)
        
        self.ip_input = QLineEdit()
        self.ip_input.setPlaceholderText("输入IP地址")
        self.ip_input.setText(common.get_ip(channel_num + 1,startNum,setp,mixVer))  # 设置默认IP
        layout.addWidget(self.ip_input)
        
        self.port_input = QLineEdit()
        self.port_input.setPlaceholderText("输入端口")
        self.port_input.setText(common.get_port(channel_num + 1,setp))  # 设置默认端口
        self.port_input.setMaximumWidth(100)
        layout.addWidget(self.port_input)
        
        self.setLayout(layout)
    
    def get_config(self):
        return {
            "channel": self.channel_num + 1,
            "ip": self.ip_input.text(),
            "port": self.port_input.text()
        }

class ChannelConfigWindow(QWidget, Ui_rpc_test):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  # 初始化UI

        self.mixVer = "mix7"

        # 结束的线程数目
        self.finished_count = 0
        #启动的线程数目
        self.start_count = 0
        
        # 初始化变量,存放ChannelConfig,通道信息的实例
        self.channel_widgets = []
        self.threads = []  # 用于存储启动的线程，便于管理
        
        # 设置SpinBox范围
        self.spinBo_get_channel.setRange(1, 30)
        self.spinBo_get_channel.setValue(8)

        #存放方法和提示词的字典
        self.methods_doc_dict = {}

        enable_drag_drop(self.lineEdit_method)
        enable_drag_drop(self.lineEdit_arg)
        enable_drag_drop(self.lineEdit_local)
        enable_drag_drop(self.lineEdit_remote)

        # 连接信号槽
        #mix7或mix8
        self.checkBox_mix8.clicked.connect(self.mix_stats_channels)
        #获取通道信息
        self.pushButton_get_channel.clicked.connect(self.update_channels)
        #允许修改通道信息
        self.checkBox_info_read.stateChanged.connect(self.toggle_lineedit_readonly)
        #建立rpc通信
        self.pushButton_connect.clicked.connect(self.start_threads)
        #rpc发送指令
        self.pushButton_send.clicked.connect(self.start_rpc)
        #rpc停止通信
        self.pushButton_stop.clicked.connect(self.stop_rpc)

        #ssh获取mix文件
        self.pushButton_getMixFile.clicked.connect(self.get_mix_file)
        #ssh上传本地文件到mix
        self.pushButton_pushMixFile.clicked.connect(self.push_mix_file)
        #ssh远程执行重启指令
        self.pushButton_reboot.clicked.connect(self.ssh_reboot)
        #ssh远程执行mix修改权限指令
        self.pushButton_chmod.clicked.connect(self.ssh_pushButton_chmod)
        #页面切换
        self.tabWidget.currentChanged.connect(self.on_tab_changed)
        #允许ssh基本信息
        self.checkBox_info_read_ssh.stateChanged.connect(self.toggle_lineedit_readonly)
        #rpc方法切换
        self.lineEdit_method.textChanged.connect(self.lookup_value)
        #运行debug脚本
        self.pushButton_run_script.clicked.connect(self.run_script)

         # 存储已加载模块避免重复导入
        self.loaded_modules = {}

        # 初始化通道
        self.update_channels()
      
    def mix_stats_channels(self):
        self.mixVer = "mix8"
        self.update_channels()
        self.lineEdit_name.setText("mixadmin")
        self.lineEdit_password.setText("mixadmin")

    def lookup_value(self,text):
        self.textEdit_doc.clear()
        cleaned_text = text.strip()
        if cleaned_text in self.methods_doc_dict:
            self.textEdit_doc.setText(self.methods_doc_dict[cleaned_text])

    def run_script(self):
        # 外部脚本路径（与可执行文件同目录）
        script_path = app_path + "debug.py"
        print(f"Loading script from: {script_path}")

        if not os.path.exists(script_path):
            QMessageBox.critical(self, "错误", f"脚本文件不存在: {script_path}")
            return

        try:
            # 生成基于文件路径的唯一模块名
            module_name = f"external_script_{abs(hash(script_path))}"

            print(f"Loading new module: {module_name}")
            spec = importlib.util.spec_from_file_location(module_name, script_path)
            if spec is None:
                raise ImportError(f"创建模块规范失败: {script_path}")

            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module  # 注册模块
            spec.loader.exec_module(module)
            self.loaded_modules[script_path] = module
            # 保存模块名以便后续重载
            self.loaded_modules[script_path].__name__ = module_name
            
            # 调用run函数
            if hasattr(module, "run"):
                module.run()
            else:
                print("Error: 'run' function not found in the script")
                
        except Exception as e:
            print(f"Error executing external script: {str(e)}")

    def on_tab_changed(self,index):
        """槽函数：标签页切换时触发"""
        print(f"切换到标签页索引: {index}")
        title = self.tabWidget.tabText(index)
        print(f"标签页标题: {title}")
        if title == "RPC":
            init_logging(self.textEdit_log,title_name="RPC")
        elif title == "SSH":
            init_logging(self.textEdit_log2,log_level=logging.WARNING,title_name="SSH")
        elif title == "script":
            init_logging(self.textEdit_log3,title_name="script")

    def push_mix_file(self):
        self.pushButton_pushMixFile.setEnabled(False)
        ssh = WorkerSSH(self)
        ssh.ssh_run("push")
        self.pushButton_pushMixFile.setEnabled(True)

    def get_mix_file(self):
        self.pushButton_getMixFile.setEnabled(False)
        ssh = WorkerSSH(self)
        ssh.ssh_run("get")
        self.pushButton_getMixFile.setEnabled(True)

    def ssh_reboot(self):
        self.pushButton_reboot.setEnabled(False)
        ssh = WorkerSSH(self)
        ssh.ssh_run("reboot")
        self.pushButton_reboot.setEnabled(True)

    def ssh_pushButton_chmod(self):
        self.pushButton_reboot.setEnabled(False)
        ssh = WorkerSSH(self)
        ssh.ssh_run("sudo")
        self.pushButton_reboot.setEnabled(True)



    def start_rpc(self):
        for thread in self.threads:
            QMetaObject.invokeMethod(thread, "send_rpc_method", Qt.QueuedConnection)

    def stop_rpc(self):
        for thread in self.threads:
            QMetaObject.invokeMethod(thread, "stop", Qt.QueuedConnection)

    def start_threads(self):
        # current_index = self.tabWidget.currentIndex()
        # logging.info(current_index)
        # 执行动作
        logging.info("run")
        self.pushButton_connect.setEnabled(False)
        self.pushButton_get_channel.setEnabled(False)
        thread_ID = 1
        for w in self.channel_widgets:
            dictBuff = w.get_config()
            thread = self.create_and_start_thread(dictBuff,thread_ID)
            self.threads.append(thread)
            thread_ID = thread_ID + 1

            slotId = f"channel_{dictBuff['channel']}" 
            logging.info(slotId+":"+dictBuff["ip"]+"  "+dictBuff["port"])

    def create_and_start_thread(self, slotInfo, thread_ID,**kwargs):
        # 封装创建并启动线程的逻辑，便于管理和扩展。
        thread = WorkerThread(self,slotInfo,thread_ID)
        thread.finished_signal.connect(lambda t=thread: self.on_thread_finished(t))  # 连接信号到槽函数
        thread.label_add_tips.connect(lambda idx=slotInfo:self.lineEditAddTips(slotInfo))
        self.start_count += 1
        thread.start()
        return thread

    def on_thread_finished(self, thread):
        self.finished_count += 1
        if thread in self.threads:
            self.threads.remove(thread)
        if self.finished_count == self.start_count:
            self.finished_count = 0
            self.start_count = 0
            logging.info("All threads have finished.")
            self.pushButton_connect.setEnabled(True)
            self.pushButton_get_channel.setEnabled(True)


    def lineEditAddTips(self,slotInfo):
        absolute_path = os.path.join(app_path, "data.json")
        print(absolute_path)
        with open(absolute_path, 'r', encoding='utf-8') as f:
            self.all_methods_dict = json.load(f)

        self.all_methods_list = list(self.all_methods_dict.keys())
        list1 = []
        for text in self.all_methods_list:
            for item in self.all_methods_dict[text]:
                self.methods_doc_dict[text+item["name"]] = item["doc"]
                # print(text+item["name"])
                list1.append(text+item["name"])
        completer = QCompleter(list1)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.lineEdit_method.setCompleter(completer)

    def toggle_lineedit_readonly(self, state):

        """根据复选框状态切换文本框只读属性"""
        is_readonly = state != Qt.Checked  # 当复选框被勾选时取消只读

        sender = self.sender()

        if sender == self.checkBox_info_read:
            self.lineEdit_one_channel_ip.setReadOnly(is_readonly)
            self.lineEdit_xavier_server_num.setReadOnly(is_readonly)
            self.lineEdit_one_channel_ip.setStyleSheet(
                "background: white;" if not is_readonly else "background: #f0f0f0;"
            )
            self.lineEdit_xavier_server_num.setStyleSheet(
                "background: white;" if not is_readonly else "background: #f0f0f0;"
            )
        elif sender == self.checkBox_info_read_ssh:
            self.lineEdit_name.setReadOnly(is_readonly)
            self.lineEdit_password.setReadOnly(is_readonly)
            self.lineEdit_name.setStyleSheet(
                "background: white;" if not is_readonly else "background: #f0f0f0;"
            )
            self.lineEdit_password.setStyleSheet(
                "background: white;" if not is_readonly else "background: #f0f0f0;"
            )

    
    def update_channels(self):
        target_count = self.spinBo_get_channel.value()
        
        # 删除多余通道
        while len(self.channel_widgets) > 0:
            widget = self.channel_widgets.pop()
            widget.deleteLater()
        
        # print("按下按钮后，通道数目"+str(len(self.channel_widgets)))
        # 添加缺少的通道
        while len(self.channel_widgets) < target_count:
            channel_num = len(self.channel_widgets)

            if self.lineEdit_one_channel_ip.text():
                lineEdit_one_channel_ip_text = self.lineEdit_one_channel_ip.text()
            else:
                lineEdit_one_channel_ip_text = 32
            if self.lineEdit_xavier_server_num.text():
                lineEdit_xavier_server_num_text = self.lineEdit_xavier_server_num.text()
            else:
                lineEdit_xavier_server_num_text = 1

            new_channel = ChannelConfig(channel_num,lineEdit_one_channel_ip_text,lineEdit_xavier_server_num_text,self.mixVer)
            self.channel_widgets.append(new_channel)
        # print("按下按钮后，又添加按钮，增加了按钮数目"+str(len(self.channel_widgets)))
        
        self.update_layout()
    
    def update_layout(self):
        # 只管理当前有效的控件
        layout = self.scrollContentWidget.layout() or QVBoxLayout(self.scrollContentWidget)
        
        # 清空布局但不删除控件（保持控件引用）
        while layout.count():
            layout.takeAt(0)
        
        # 仅添加当前有效的控件
        for w in self.channel_widgets:
            if w.isWidgetType():  # 确保控件未失效
                layout.addWidget(w)
        
        layout.addStretch()
    
    def get_configs(self):
        return [channel.get_config() for channel in self.channel_widgets]
        
    def closeEvent(self, event):
        for thread in self.threads:
            if thread.isRunning():
                thread.stop()
                thread.quit()
                thread.wait()
        self.threads.clear()
        # 移除日志处理器
        logger = logging.getLogger()
        for handler in logger.handlers[:]:
            if isinstance(handler, QTextEditLogger):
                logger.removeHandler(handler)
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ChannelConfigWindow()
    init_logging(window.textEdit_log)
    window.show()
    sys.exit(app.exec_())